/**
 * Rodimiro Andrés Castañeda Calderón (12122)
 * Mario Roberto Gómez Flores (12165)
 * Sección 10
 * 22/07/2013
 * Postfix.java
 * Clase con metodos para realizar una operacion en formato Postfix. Recibe como entrada la operacion en un String
 */

public class Postfix {
    //atributos
    private iPila<Integer> operacion;
    private String operacion_txt;

    /**
     * Constructor de una calculadora postfix.
     * @param operacion_txt
     * String con la operacion en formato postfix
     */
    public Postfix(String operacion_txt){
        this.operacion_txt = operacion_txt;
        operacion = (iPila<Integer>)(new PilaMario<Integer>());
    }

    /**
     * Realiza la operación almacenada en la calculadora.
     * @return
     * El resultado de la operación almacenada
     */
    public int realizarOperacion(){
        //Llama a operarEspacio(int index) para empezar a operar
        operarEspacio(0);

        //Cuando termina operarEspacio(), si saca un Integer de la pila
        Integer resultado = operacion.pop();

        //Y luego ya no queda ningun elemento
        if(! operacion.hasItems()){
            //Todo termino bien y devuelve el valor de resultado.
            return resultado.intValue();
        }
        //Si todavia tiene elementos
        else{
            //Significa que habian mas numeros que operaciones
            throw new IncompleteOperationException(
                    "No hay suficientes operadores para utilizar todos los números. Revisar el archivo fuente."
                    );
        }
    }

    /**
     * Método recursivo para realizar la operación char por char.
     * @param index
     * El index del char que se operará
     * <dt><b> Precondition: </b><dd>
     * 0 <= index <= operacion.length;
     */
    private void operarEspacio(int index){
        //Si index es menor que el largo del String
        if(index < operacion_txt.length()){
            //Obtiene el char en (index)
            String caracter = operacion_txt.charAt(index)+"";

            //Trata de convertir el caracter a numero
            try{
                Integer numero = new Integer(caracter);

                //Si puede convertirlo, lo agrega a la pila
                operacion.push(numero);
            }
            catch(NumberFormatException nfe){
                //Si no puede convertirlo, talvez era un operador
                Integer[] operandos;
                //Suma
                if(caracter.equals("+")){
                    operandos = obtenerOperandos();
                    operacion.push(operandos[0]+operandos[1]);
                }
                //Resta
                else if(caracter.equals("-")){
                    operandos = obtenerOperandos();
                    operacion.push(operandos[0]-operandos[1]);
                }
                //Multiplicacion
                else if(caracter.equals("*")){
                    operandos = obtenerOperandos();
                    operacion.push(operandos[0]*operandos[1]);
                }
                //Division
                else if(caracter.equals("/")){
                    operandos = obtenerOperandos();
                    operacion.push(operandos[0]/operandos[1]);
                }
                //Si era un espacio vacio, no hace nada
                else if(caracter.equals(" ")){
                }
                //Si era cualquier otra cosa
                else{
                    //El archivo esta mal escrito y lanza UnsupportedOperationException
                    throw new UnsupportedOperationException("Caracter no reconocido: "+caracter+". Revisar el archivo fuente.");
                }
            }

            //Cuando sale, se llama a si mismo con el indice aumentado
            operarEspacio(index+1);
        }
        //Si es negativo, o no es menor que el largo del String, no hace nada
    }

    /**
     * Saca dos operaciones de la pila.
     * @return 
     * Pareja ordenada de los operandos más recientes.
     */
    private Integer[] obtenerOperandos(){
        //Lista para almacenar los operandos
        Integer[] operandos = new Integer[2];

        /*
         * NOTA:
         * No se uso un ciclo for porque todas las operaciones
         * definidas en este programa requieren exactamente 
         * dos operandos. Aunque no sea convencional, pero estas
         * instrucciones son más eficiente que un ciclo for y
         * parte de los objetivos del ejercicio es intentar
         * optimizar el algoritmo.
         * Ademas, la prueba profiler(1).png muestra claramente que este metodo
         * es mas eficiente así que con un ciclo for.
         */

        //Obtiene los dos operandos de la pila
        //Los saca al reves porque el primer operando fue ingresado primero y sale despues
        operandos[1] = operacion.pop();
        operandos[0] = operacion.pop();

        //Devuelve los operandos
        return operandos;
    }
}
